
#include "etl_socket.h"

#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#ifdef WIN32
#include <winsocmf.h>
#else
#include <sys/socket.h>
#endif

#include "etl_socket.h"

using namespace etl;

#define DEFAULT_RECV_TIMEOUT 5000
#define DEFAULT_SEND_TIMEOUT 5000

const int MAX_CONNECTIONS = 5;

etl_socket::etl_socket() : 
  sok(-1),
  recv_timeout_milliseconds( DEFAULT_RECV_TIMEOUT ),
  recv_timeout_handler( NULL ),
  recv_timeout_handler_opaque( NULL ),
  send_timeout_milliseconds( DEFAULT_SEND_TIMEOUT ),
  send_timeout_handler( NULL ),
  send_timeout_handler_opaque( NULL )
{
  memset(&addr, 0, sizeof(sockaddr_in));
}

etl_socket::~etl_socket()
{ 
  if( sok != -1 )
    ::close( sok );
}

int etl_socket::create()
{
  sok = ::socket(AF_INET, (int) SOCK_STREAM, 0);

  int on = 1;

  return (setsockopt(sok, SOL_SOCKET, SO_REUSEADDR, (const char *) &on,
                     sizeof(int)) == -1)
       ? SOK_FAILURE
       : SOK_SUCCESS;
}

int etl_socket::get_sok_id()
{
  return sok;
}

int etl_socket::bind(int port)
{
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(port);

  return (::bind(sok, (struct sockaddr *) &addr, sizeof(addr)) == -1)
       ? SOK_FAILURE
       : SOK_SUCCESS;
}

int etl_socket::listen()
{
  return (::listen(sok, MAX_CONNECTIONS) == -1) ? SOK_FAILURE : SOK_SUCCESS;
}

int etl_socket::accept(etl_socket& new_etl_socket)
{
  int addr_length = sizeof(addr);
#ifdef WIN32
  return ((new_etl_socket.sok = ::accept(sok, (sockaddr *) &addr,
                                           (int *) &addr_length))
       <= 0)
       ? SOK_FAILURE
       : SOK_SUCCESS;
#else
  return ((new_etl_socket.sok = ::accept(sok, (sockaddr *) &addr,
                                           (socklen_t *) &addr_length))
       <= 0)
       ? SOK_FAILURE
       : SOK_SUCCESS;
#endif
}

int etl_socket::connect(etl_string host, int port)
{
  if (sok == -1)
    return SOK_FAILURE;

  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);

#ifdef WIN32
#else
  if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) == -1)
    return SOK_FAILURE;
#endif

  return (::connect(sok, (sockaddr *) &addr, sizeof(sockaddr_in)) == -1)
       ? SOK_FAILURE
       : SOK_SUCCESS;
}

int etl_socket::shutdown( int mode )
{
  return ::shutdown( sok, mode );
}

int etl_socket::send( const void* msg, ssize_t msg_len )
{
  if( msg && (msg_len != 0) )
    {
      unsigned char* byte_ptr = (unsigned char*)msg;

      ssize_t bytes_to_write = msg_len;

      while( bytes_to_write > 0 )
	{
	  if( wait_send() == SOK_FAILURE )
	    return SOK_FAILURE;
#ifdef WIN32
	  ssize_t bytes_just_written = ::send( sok, byte_ptr, bytes_to_write, 0 );
#else
	  // make sure I need MSG_NOSIGNAL
	  ssize_t bytes_just_written = ::send( sok, byte_ptr, bytes_to_write, MSG_NOSIGNAL );
#endif
	  if( bytes_just_written == -1 )
	    return SOK_FAILURE;
	  else
	    {
	      bytes_to_write -= bytes_just_written;
	      byte_ptr += bytes_just_written;
	    }
	}
      return SOK_SUCCESS;
    }
  return SOK_FAILURE;
}

int etl_socket::recv( void* buf, ssize_t buf_len )
{
  if( buf && (buf_len != 0) )
    {
      unsigned char* byte_ptr = (unsigned char*)buf;

      ssize_t bytes_to_read = buf_len;

      while( bytes_to_read > 0 )
	{
	  if( wait_recv() == SOK_FAILURE )
	    return SOK_FAILURE;

	  ssize_t bytes_just_read = ::recv( sok, byte_ptr, bytes_to_read, 0 );

	  if( bytes_just_read == -1 )
	    return SOK_FAILURE;
	  else
	    {
	      bytes_to_read -= bytes_just_read;
	      byte_ptr += bytes_just_read;
	    }
	}
      return SOK_SUCCESS;
    }
  return SOK_FAILURE;
}

ssize_t etl_socket::raw_send( const void* msg, size_t msg_len )
{
#ifdef WIN32
  return (::send(sok, msg, msg_len, 0) == -1) ? SOK_FAILURE : SOK_SUCCESS;
#else
  return (::send(sok, msg, msg_len, MSG_NOSIGNAL) == -1)
       ? SOK_FAILURE
       : SOK_SUCCESS;
#endif
}

ssize_t etl_socket::raw_recv( void* buf, size_t buf_len )
{
  return (::recv(sok, buf, buf_len, 0) == -1) ? SOK_FAILURE : SOK_SUCCESS;
}

int etl_socket::attach_recv_timeout_handler( timeout_cb rtcb, void* opaque )
{
  recv_timeout_handler        = rtcb;
  recv_timeout_handler_opaque = opaque;
  return SOK_SUCCESS;
}

int etl_socket::set_recv_timeout( size_t milliseconds )
{
  recv_timeout_milliseconds = milliseconds;
  return SOK_SUCCESS;
}

int etl_socket::attach_send_timeout_handler( timeout_cb stcb, void* opaque )
{
  send_timeout_handler        = stcb;
  send_timeout_handler_opaque = opaque;
  return SOK_SUCCESS;
}

int etl_socket::set_send_timeout( size_t milliseconds )
{
  send_timeout_milliseconds = milliseconds;
  return SOK_SUCCESS;
}

bool etl_socket::ready_to_recv( size_t milliseconds )
{
  fd_set read_fds;

  FD_ZERO( &read_fds );

  FD_SET( sok, &read_fds );

  struct timeval recv_timeout = { 0, 0 };

  recv_timeout.tv_usec = milliseconds * 1000;

  if( recv_timeout.tv_usec > 1000000 )
    recv_timeout.tv_sec  = recv_timeout.tv_usec / 1000000;
  else recv_timeout.tv_sec = 0;

  if( recv_timeout.tv_sec > 0 )
    recv_timeout.tv_usec = recv_timeout.tv_usec % 1000000;

  if( select( sok+1, &read_fds, NULL, NULL, &recv_timeout ) > 0 )
    return true;

  return false;
}

bool etl_socket::ready_to_send( size_t milliseconds )
{
  fd_set write_fds;

  FD_ZERO( &write_fds );

  FD_SET( sok, &write_fds );

  struct timeval send_timeout = { 0, 0 };

  send_timeout.tv_usec = milliseconds * 1000;

  if( send_timeout.tv_usec > 1000000 )
    send_timeout.tv_sec  = send_timeout.tv_usec / 1000000;
  else send_timeout.tv_sec = 0;

  if( send_timeout.tv_sec > 0 )
    send_timeout.tv_usec = send_timeout.tv_usec % 1000000;

  if( select( sok+1, NULL, &write_fds, NULL, &send_timeout ) > 0 )
    return true;

  return false;
}

int etl_socket::wait_recv()
{
 RETRY:
  bool timeout = false;

  fd_set read_fds;

  FD_ZERO( &read_fds );

  FD_SET( sok, &read_fds );

  struct timeval recv_timeout;

  recv_timeout.tv_usec = recv_timeout_milliseconds * 1000;

  if( recv_timeout.tv_usec > 1000000 )
    recv_timeout.tv_sec  = recv_timeout.tv_usec / 1000000;
  else recv_timeout.tv_sec = 0;

  if( recv_timeout.tv_sec > 0 )
    recv_timeout.tv_usec = recv_timeout.tv_usec % 1000000;

   if( select( sok+1, &read_fds, NULL, NULL, &recv_timeout ) == -1 )
    {
      timeout = true;
    }

  if( !FD_ISSET( sok, &read_fds ) )
    {
      timeout = true;
    }

  if( timeout )
    {
      if( recv_timeout_handler && (recv_timeout_handler( recv_timeout_handler_opaque ) == -1) )
	goto RETRY;

      return SOK_FAILURE;
    }

  return SOK_SUCCESS;
}

int etl_socket::wait_send()
{
 RETRY:
  bool timeout = false;

  fd_set write_fds;

  FD_ZERO( &write_fds );

  FD_SET( sok, &write_fds );

  struct timeval send_timeout;

  send_timeout.tv_usec = send_timeout_milliseconds * 1000;

  if( send_timeout.tv_usec > 1000000 )
    send_timeout.tv_sec  = send_timeout.tv_usec / 1000000;
  else send_timeout.tv_sec = 0;

  if( send_timeout.tv_sec > 0 )
    send_timeout.tv_usec = send_timeout.tv_usec % 1000000;

   if( select( sok+1, NULL, &write_fds, NULL, &send_timeout ) == -1 )
    {
      timeout = true;
    }

  if( !FD_ISSET( sok, &write_fds ) )
    {
      timeout = true;
    }

  if( timeout )
    {
      if( send_timeout_handler && (send_timeout_handler( send_timeout_handler_opaque ) == -1) )
	goto RETRY;

      return SOK_FAILURE;
    }

  return SOK_SUCCESS;
}
